﻿#include "framework.h"
#include <time.h>
#include "Game.h"

// 函数声明
static void Game_Delete(Game*);
static void Game_AfterMove(Game*, BOOL);
static void Game_CreateRandomNumber(Game*);
static void Game_DrawAllNumbers(Game*);
static void Game_DrawGameBoard(Game*);
static void Game_DrawOneNumber(Game*, int, int, int);
static void Game_GameOver(Game*, BOOL);
static BOOL Game_IsLost(Game*);
static BOOL Game_IsWon(Game*);
static void Game_MoveDown(Game*);
static void Game_MoveLeft(Game*);
static void Game_MoveRight(Game*);
static void Game_MoveUp(Game*);
static BOOL Game_OnKeyPress(Game*, UINT);
static void Game_Start(Game*);

/**
 * 创建游戏。
 * @param callback  回调
 * @param drawer    绘图者
 * @param gameBoard 游戏区域
 * @return 游戏
 */
Game* New_Game(HWND callback, HDC drawer, HWND gameBoard) {
    // 初始化数据
    Game* game = (Game*)calloc(1, sizeof(Game));
    LOGFONT lf = { GAME_BOARD_FONT_SIZE };

    game->mCallback = callback;
    game->mDrawer = drawer;

    game->mFont = CreateFontIndirect(&lf);
    game->mGameBoard = gameBoard;
    game->mStatus = END;

    SetBkMode(game->mDrawer, TRANSPARENT);
    SelectObject(game->mDrawer, game->mFont);

    // 初始化函数
    game->deleteSelf = Game_Delete;
    game->onKeyPressed = Game_OnKeyPress;
    game->start = Game_Start;

    // 重绘游戏区域
    Game_DrawGameBoard(game);

    // 置随机数种子
    srand((UINT)time(NULL));

    return game;
}

/**
 * 删除游戏。
 * @param game  游戏
 */
static void Game_Delete(Game* game) {
    DeleteObject(game->mFont);
    free(game);
}

/**
 * 移动之后要做的事。
 * @param game  游戏
 * @param changed   指示移动之后，每个格上的数字是否改变。任意一格有改变则为 TRUE，否则为 FALSE
 */
static void Game_AfterMove(Game* game, BOOL changed) {
    // 重绘游戏区域
    Game_DrawGameBoard(game);

    // 画所有数字
    Game_DrawAllNumbers(game);

    // 如果胜利或失败，则游戏结束
    if (Game_IsWon(game)) {
        Game_GameOver(game, TRUE);
        return;
    }
    if (Game_IsLost(game)) {
        Game_GameOver(game, FALSE);
        return;
    }

    // 创建下一个数
    if (changed) {
        Game_CreateRandomNumber(game);
    }
}

/**
 * 在所有格中，随机找到未被使用的一个，将格上的数字设为2或4。
 * @param game  游戏
 */
static void Game_CreateRandomNumber(Game* game) {
    int row = rand() % GAME_BOARD_GRID_COUNT_V;
    int line = rand() % GAME_BOARD_GRID_COUNT_H;

    while (game->mNumbers[row][line] != 0) {
        row = rand() % GAME_BOARD_GRID_COUNT_V;
        line = rand() % GAME_BOARD_GRID_COUNT_H;
    }

    game->mNumbers[row][line] = (rand() % 8) <= 3 ? 2: 4;
    Game_DrawOneNumber(game, line, row, game->mNumbers[row][line]);
    InvalidateRect(game->mGameBoard, NULL, FALSE);
}

/**
 * 画所有数字。
 * @param game  游戏
 */
static void Game_DrawAllNumbers(Game* game) {
    int i = 0, j = 0;
    for (i = 0; i < GAME_BOARD_GRID_COUNT_V; i++) {
        for (j = 0; j < GAME_BOARD_GRID_COUNT_H; j++) {
            if (game->mNumbers[i][j] != 0) {
                Game_DrawOneNumber(game, j, i, game->mNumbers[i][j]);
            }
        }
    }

    InvalidateRect(game->mGameBoard, NULL, FALSE);
}

/**
 * 画游戏区域。
 * @param game  游戏
 */
static void Game_DrawGameBoard(Game* game) {
    int i = 0;
    RECT rect = { 1, 1, GAME_BOARD_WIDTH - 1, GAME_BOARD_HEIGHT - 1 };

    // 画边框
    Rectangle(game->mDrawer, 0, 0, GAME_BOARD_WIDTH, GAME_BOARD_HEIGHT);

    // 画背景
    SetBkColor(game->mDrawer, GAME_BOARD_COLOR);
    ExtTextOut(game->mDrawer, 0, 0, ETO_OPAQUE, &rect, NULL, 0, NULL);

    // 画竖线
    for (i = 1; i < GAME_BOARD_GRID_COUNT_V; i++) {
        MoveToEx(game->mDrawer, GRID_SIZE * i, 0, NULL);
        LineTo(game->mDrawer, GRID_SIZE * i, GAME_BOARD_HEIGHT);
    }

    // 画横线
    for (i = 1; i < GAME_BOARD_GRID_COUNT_H; i++) {
        MoveToEx(game->mDrawer, 0, GRID_SIZE * i, NULL);
        LineTo(game->mDrawer, GAME_BOARD_WIDTH, GRID_SIZE * i);
    }

    // 通知游戏区域重绘
    InvalidateRect(game->mGameBoard, NULL, FALSE);
}

/**
 * 画单个数字。
 * @param game      游戏
 * @param line      列
 * @param row       行
 * @param number    数字
 */
static void Game_DrawOneNumber(Game* game, int line, int row, int number) {
    TCHAR text[STRING_BUF_LEN] = { 0 };
    RECT rect = { 0 };

    // 计算位置
    rect.left = line * GRID_SIZE;
    rect.top = row * GRID_SIZE;
    rect.right = rect.left + GRID_SIZE;
    rect.bottom = rect.top + GRID_SIZE;

    // 画数字
    _stprintf_s(text, STRING_BUF_LEN, _T("%d"), number);
    DrawText(game->mDrawer, text, (int)_tcslen(text), &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

/**
 * 游戏结束。
 * @param game  游戏
 * @param isWon 胜利则为 TRUE，否则为 FALSE
 */
static void Game_GameOver(Game* game, BOOL isWon) {
    // 将状态设置为已结束
    game->mStatus = END;

    // 发送通知
    SendMessage(game->mCallback, UM_GAME_OVER, isWon, 0);
}

/**
 * 判断是否失败。
 * @param game  游戏
 * @return 失败则返回 TRUE，否则返回 FALSE
 */
static BOOL Game_IsLost(Game* game) {
    int i = 0, j = 0;   // 循环变量

    // 横向检查所有数字
    for (i = 0; i < GAME_BOARD_GRID_COUNT_V; i++) {
        for (j = 0; j < GAME_BOARD_GRID_COUNT_H - 1; j++) {
            if (game->mNumbers[i][j] == 0 || game->mNumbers[i][j] == game->mNumbers[i][j + 1]) {
                return FALSE;
            }
        }
    }

    // 纵向检查所有数字
    for (j = 0; j < GAME_BOARD_GRID_COUNT_H; j++) {
        for (i = 0; i < GAME_BOARD_GRID_COUNT_V - 1; i++) {
            if (game->mNumbers[i][j] == game->mNumbers[i + 1][j]) {
                return FALSE;
            }
        }
    }

    // 判断最右边一列是否有零
    for (i = 0; i < GAME_BOARD_GRID_COUNT_V; i++) {
        if (game->mNumbers[i][3] == 0) {
            return FALSE;
        }
    }

    return TRUE;
}

/**
 * 判断是否胜利。
 * @param game  游戏
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
static BOOL Game_IsWon(Game* game) {
    // 检查所有数字
    for (int i = 0; i < GAME_BOARD_GRID_COUNT_V; i++) {
        for (int j = 0; j < GAME_BOARD_GRID_COUNT_H; j++) {
            if (game->mNumbers[i][j] == WIN_NUMBER) {
                return TRUE;
            }
        }
    }

    return FALSE;
}
/**
 * 下移。
 * @param game  游戏
 */
static void Game_MoveDown(Game* game) {
    int i, j, k;            // 循环变量
    int ignore = -1;        // 忽略的格
    BOOL changed = FALSE;   // 是否相加、移动

    // 从左到右遍历每一列
    for (j = 0; j < GAME_BOARD_GRID_COUNT_H; j++) {
        // 每列从上到下遍历每一格，判断是否有相同数，有则相加
        for (i = 0; i < GAME_BOARD_GRID_COUNT_V; i++) {
            // 如果该数为零或之前被加过，则什么也不做
            if (game->mNumbers[i][j] == 0 || i == ignore) {
                continue;
            }

            // 不为零则从该数下方逐个检查
            for (k = i + 1; k < GAME_BOARD_GRID_COUNT_V; k++) {
                // 遇到零跳过
                if (game->mNumbers[k][j] == 0) {
                    continue;
                }

                // 如果两数不相等，则结束本次循环
                if (game->mNumbers[k][j] != game->mNumbers[i][j]) {
                    break;
                }

                // 两数相等则相加
                game->mNumbers[k][j] += game->mNumbers[i][j];
                game->mNumbers[i][j] = 0;
                ignore = k;
                changed = TRUE;
            }
        }

        // 所有数字向下移动
        for (i = GAME_BOARD_GRID_COUNT_V - 2; i >= 0; i--) {
            // 如果该数为零（没有数），则什么也不做
            if (game->mNumbers[i][j] == 0) {
                continue;
            }

            // 不为零则向下移动
            for (k = i + 1; k < GAME_BOARD_GRID_COUNT_V; k++) {
                if (game->mNumbers[k][j] == 0) {
                    game->mNumbers[k][j] = game->mNumbers[k - 1][j];
                    game->mNumbers[k - 1][j] = 0;
                    changed = TRUE;
                }
            }
        }
    }

    Game_AfterMove(game, changed);
}

/**
 * 左移。
 * @param game  游戏
 */
static void Game_MoveLeft(Game* game) {
    int i, j, k;            // 循环变量
    int ignore = -1;        // 忽略的格
    BOOL changed = FALSE;   // 是否相加、移动

    // 从上到下遍历每一行
    for (i = 0; i < GAME_BOARD_GRID_COUNT_V; i++) {
        // 每行从右到左遍历每一格，判断是否有相同数，有则相加
        for (j = GAME_BOARD_GRID_COUNT_H - 1; j > 0; j--) {
            // 如果该数为零或之前被加过，则什么也不做
            if (game->mNumbers[i][j] == 0 || j == ignore) {
                continue;
            }

            // 不为零则从该数左面逐个检查
            for (k = j - 1; k >= 0; k--) {
                // 遇到零跳过
                if (game->mNumbers[i][k] == 0) {
                    continue;
                }

                // 如果两数不相等，则结束本次循环
                if (game->mNumbers[i][k] != game->mNumbers[i][j]) {
                    break;
                }

                // 两数相等则相加
                game->mNumbers[i][k] += game->mNumbers[i][j];
                game->mNumbers[i][j] = 0;
                ignore = k;
                changed = TRUE;
            }
        }

        // 所有数字向左移动
        for (j = 1; j < GAME_BOARD_GRID_COUNT_H; j++) {
            // 如果该数为零（没有数），则什么也不做
            if (game->mNumbers[i][j] == 0) {
                continue;
            }

            // 不为零则向左移动
            for (k = j - 1; k >= 0; k--) {
                if (game->mNumbers[i][k] == 0) {
                    game->mNumbers[i][k] = game->mNumbers[i][k + 1];
                    game->mNumbers[i][k + 1] = 0;
                    changed = TRUE;
                }
            }
        }
    }

    Game_AfterMove(game, changed);
}

/**
 * 右移。
 * @param game  游戏
 */
static void Game_MoveRight(Game* game) {
    int i, j, k;            // 循环变量
    int ignore = -1;        // 忽略的格
    BOOL changed = FALSE;   // 是否相加、移动

    // 从上到下遍历每一行
    for (i = 0; i < GAME_BOARD_GRID_COUNT_V; i++) {
        // 每行从左到右遍历每一格，判断是否有相同数，有则相加
        for (j = 0; j < GAME_BOARD_GRID_COUNT_H - 1; j++) {
            // 如果该数为零或之前被加过，则什么也不做
            if (game->mNumbers[i][j] == 0 || j == ignore) {
                continue;
            }

            // 不为零则从该数右面逐个检查
            for (k = j + 1; k < GAME_BOARD_GRID_COUNT_H; k++) {
                // 遇到零跳过
                if (game->mNumbers[i][k] == 0) {
                    continue;
                }

                // 如果两数不相等，则结束本次循环
                if (game->mNumbers[i][k] != game->mNumbers[i][j]) {
                    break;
                }

                // 两数相等则相加
                game->mNumbers[i][k] += game->mNumbers[i][j];
                game->mNumbers[i][j] = 0;
                ignore = k;
                changed = TRUE;
            }
        }

        // 所有数字向右移动
        for (j = GAME_BOARD_GRID_COUNT_H - 2; j >= 0; j--) {
            // 如果该数为零（没有数），则什么也不做
            if (game->mNumbers[i][j] == 0) {
                continue;
            }

            // 不为零则向右移动
            for (k = j + 1; k < GAME_BOARD_GRID_COUNT_H; k++) {
                if (game->mNumbers[i][k] == 0) {
                    game->mNumbers[i][k] = game->mNumbers[i][k - 1];
                    game->mNumbers[i][k - 1] = 0;
                    changed = TRUE;
                }
            }
        }
    }

    Game_AfterMove(game, changed);
}

/**
 * 上移。
 * @param game  游戏
 */
static void Game_MoveUp(Game* game) {
    int i, j, k;            // 循环变量
    int ignore = -1;        // 忽略的格
    BOOL changed = FALSE;   // 是否相加、移动

    // 从左到右遍历每一列
    for (j = 0; j < GAME_BOARD_GRID_COUNT_H; j++) {
        // 每列从下到上遍历每一格，判断是否有相同数，有则相加
        for (i = GAME_BOARD_GRID_COUNT_V - 1; i > 0; i--) {
            // 如果该数为零或之前被加过，则什么也不做
            if (game->mNumbers[i][j] == 0 || i == ignore) {
                continue;
            }

            // 不为零则从该数上方逐个检查
            for (k = i - 1; k >= 0; k--) {
                // 遇到零跳过
                if (game->mNumbers[k][j] == 0) {
                    continue;
                }

                // 如果两数不相等，则结束本次循环
                if (game->mNumbers[k][j] != game->mNumbers[i][j]) {
                    break;
                }

                // 两数相等则相加
                game->mNumbers[k][j] += game->mNumbers[i][j];
                game->mNumbers[i][j] = 0;
                ignore = k;
                changed = TRUE;
            }
        }

        // 所有数字向上移动
        for (i = 1; i < GAME_BOARD_GRID_COUNT_V; i++) {
            // 如果该数为零（没有数），则什么也不做
            if (game->mNumbers[i][j] == 0) {
                continue;
            }

            // 不为零则向上移动
            for (k = i - 1; k >= 0; k--) {
                if (game->mNumbers[k][j] == 0) {
                    game->mNumbers[k][j] = game->mNumbers[k + 1][j];
                    game->mNumbers[k + 1][j] = 0;
                    changed = TRUE;
                }
            }
        }
    }

    Game_AfterMove(game, changed);
}

/**
 * 按键事件的响应函数。
 * @param game  游戏
 * @param key   按键
 * @return 事件在此被处理则返回 TRUE，否则 FALSE
 */
static BOOL Game_OnKeyPress(Game* game, UINT key) {
    if (game->mStatus != PLAYING) {
        return FALSE;
    }

    switch (key) {
        case VK_DOWN:
            Game_MoveDown(game);
            return TRUE;
        case VK_LEFT:
            Game_MoveLeft(game);
            return TRUE;
        case VK_RIGHT:
            Game_MoveRight(game);
            return TRUE;
        case VK_UP:
            Game_MoveUp(game);
            return TRUE;
    }

    return FALSE;
}

/**
 * 游戏开始。
 * @param game  游戏
 */
static void Game_Start(Game* game) {
    // 重绘游戏区域
    Game_DrawGameBoard(game);

    // 数字矩阵清零
    ZeroMemory(game->mNumbers, sizeof(game->mNumbers));

    // 创建随机数字
    Game_CreateRandomNumber(game);

    // 将状态设置为游戏中
    game->mStatus = PLAYING;
}
